package cn.tedu.mall.order.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.mapper.OmsOrderItemMapper;
import cn.tedu.mall.order.mapper.OmsOrderMapper;
import cn.tedu.mall.order.service.IOmsCartService;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.order.utils.IdGeneratorUtils;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderListTimeDTO;
import cn.tedu.mall.pojo.order.dto.OrderStateUpdateDTO;
import cn.tedu.mall.pojo.order.model.OmsCart;
import cn.tedu.mall.pojo.order.model.OmsOrder;
import cn.tedu.mall.pojo.order.model.OmsOrderItem;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.order.vo.OrderDetailVO;
import cn.tedu.mall.pojo.order.vo.OrderListVO;
import cn.tedu.mall.product.service.order.IForOrderSkuService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

// 订单管理模块的业务逻辑层实现类,后面秒杀业务需要Dubbo调用,所以注册到Nacos
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {

    // Dubbo调用product模块减少库存的方法
    @DubboReference
    private IForOrderSkuService dubboSkuService;
    @Autowired
    private IOmsCartService omsCartService;
    @Autowired
    private OmsOrderMapper omsOrderMapper;
    @Autowired
    private OmsOrderItemMapper omsOrderItemMapper;

    // 新增订单的方法
    // 这个方法因为dubbo调用了product模块的功能,操作了数据库
    // 所以有分布式的操作需求,必须使用seata来保证数据完整性
    @GlobalTransactional
    @Override
    public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
        // 第一部分: 收集信息,准备数据
        // 先收集订单信息,实例化OmsOrder对象
        OmsOrder order=new OmsOrder();
        // 将参数OrderAddDTO同名属性赋值给order对象
        BeanUtils.copyProperties(orderAddDTO,order);
        // 参数orderAddDTO中信息并不完整,有些信息需要我们生成或收集
        // 编写一个专门收集信息的方法,完成信息收集的工作
        loadOrder(order);
        // 到此为止,order对象的所有属性就收集完毕了
        // 下面要将参数orderAddDTO中包含的订单项的集合:orderItems进行信息的收集工作
        List<OrderItemAddDTO> orderItems=orderAddDTO.getOrderItems();
        if(orderItems == null  ||  orderItems.isEmpty()){
            // 如果参数orderAddDTO中包含的订单项集合是null或没有元素
            // 抛出异常终止程序
            throw new CoolSharkServiceException(
                    ResponseCode.BAD_REQUEST,"订单中至少要包含一件商品");
        }
        // 我们最终目标是将订单项信息保存到数据库
        // 数据库中编写的方法参数类型是List<OmsOrderItem>
        // 所以我们要遍历orderItems集合中的每个元素,转换为OmsOrderItem
        // 然后再保存再List<OmsOrderItem>类型的集合中才能完成订单项的新增
        // 先实例化List<OmsOrderItem>的集合
        List<OmsOrderItem> omsOrderItems=new ArrayList<>();
        // 遍历DTO中的集合
        for(OrderItemAddDTO addDTO : orderItems){
            // 实例化目标对象OmsOrderItem
            OmsOrderItem omsOrderItem=new OmsOrderItem();
            // 将addDTO的同名属性赋值到omsOrderItem对象中
            BeanUtils.copyProperties(addDTO,omsOrderItem);
            // 将addDTO对象没有的id属性和orderId属性赋值
            // id属性还是通过Leaf获取
            Long itemId=IdGeneratorUtils.getDistributeId("order_item");
            omsOrderItem.setId(itemId);
            // 订单id直接从order对象中获取
            omsOrderItem.setOrderId(order.getId());
            omsOrderItems.add(omsOrderItem);
            // 第二部分: 执行新增订单相关数据库操作指令
            // 1.减少库存
            // 当前正在遍历的对象是一个包含skuId和购买数量的对象
            // 先获取skuId
            Long skuId=omsOrderItem.getSkuId();
            // dubbo调用product模块写好的减少库存的方法
            int num = dubboSkuService.
                    reduceStockNum(skuId, omsOrderItem.getQuantity());
            // 判断num是否为0
            if(num == 0){
                log.error("商品库存不足,skuId:{}",skuId);
                // 抛出异常,终止程序,触发seata分布式事务回滚
                throw new CoolSharkServiceException(
                        ResponseCode.BAD_REQUEST,"库存不足");
            }
            // 2.删除用户在购物车中勾选的商品信息
            OmsCart omsCart=new OmsCart();
            omsCart.setUserId(order.getUserId());
            omsCart.setSkuId(skuId);
            // 执行删除
            omsCartService.removeUserCarts(omsCart);
        }
        // 3.执行新增订单
        omsOrderMapper.insertOrder(order);
        // 4.执行新增订单项(批量新增集合中所有订单项数据)
        omsOrderItemMapper.insertOrderItemList(omsOrderItems);
        // 第三部分: 准备返回值,返回给前端
        // 实例化返回值类型对象
        OrderAddVO addVO=new OrderAddVO();
        addVO.setId(order.getId());
        addVO.setSn(order.getSn());
        addVO.setCreateTime(order.getGmtCreate());
        addVO.setPayAmount(order.getAmountOfActualPay());
        // 最后千万别忘了返回!!!!
        return addVO;
    }
    // 为order对象生成\补全属性值的方法
    private void loadOrder(OmsOrder order) {
        // 先通过Leaf分布式序列化生成系统来获取id
        Long id= IdGeneratorUtils.getDistributeId("order");
        order.setId(id);
        // 生成用户看到的订单编号,也就是UUID
        order.setSn(UUID.randomUUID().toString());
        // 赋值userId
        // 以后完成的秒杀业务需要调用新增订单的方法,userId属性是有值的
        // 被远程调用时,当前模块无法获取用户登录信息,所以只有userId属性没有值时才需要我们赋值
        if(order.getUserId()==null){
            // SpringSecurity上下文中获取用户id
            order.setUserId(getUserId());
        }
        // 可以将其他的可能为null的属性赋值
        // 订单状态可能为null,判断订单的状态,如果为null默认赋值0
        if(order.getState()==null){
            order.setState(0);
        }
        // 为了保证当前下单时间gmt_order和数据生成时间gmt_create一致
        // 我们为下列属性赋相同的值
        LocalDateTime now= LocalDateTime.now();
        order.setGmtOrder(now);
        order.setGmtCreate(now);
        order.setGmtModified(now);

        // 后端代码可以对前端传入的金额进行验算
        // 验算公式: 实际支付金额=原价-优惠+运费
        // 使用BigDecimal类型防止浮点偏移带来的误差
        BigDecimal price=order.getAmountOfOriginalPrice();
        BigDecimal freight=order.getAmountOfFreight();
        BigDecimal discount=order.getAmountOfDiscount();
        BigDecimal actualPay=price.subtract(discount).add(freight);
        // 赋值到order对象中
        order.setAmountOfActualPay(actualPay);

    }

    // 根据订单id,修改订单状态
    @Override
    public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
        // 先实例化OmsOrder对象
        OmsOrder order=new OmsOrder();
        // orderStateUpdateDTO参数中,id和state属性赋值到order对象
        BeanUtils.copyProperties(orderStateUpdateDTO,order);
        // 调用动态修改方法,按给定的id做条件,修改订单状态为state的值
        omsOrderMapper.updateOrderById(order);
    }

    @Override
    public JsonPage<OrderListVO> listOrdersBetweenTimes(
            OrderListTimeDTO orderListTimeDTO) {
        // 方法开始,需要在运行查询前先判断用户给定的时间范围
        // 判断orderListTimeDTO中开始时间和结束时间是否有效,编写一个方法专门做这个业务
        validateTimes(orderListTimeDTO);
        // 设置分页条件
        PageHelper.startPage(orderListTimeDTO.getPage(),
                                orderListTimeDTO.getPageSize());
        // 将用户id赋值到orderListTimeDTO中
        orderListTimeDTO.setUserId(getUserId());
        // 执行查询
        List<OrderListVO> list=omsOrderMapper
                .selectOrdersBetweenTimes(orderListTimeDTO);
        // 别忘了返回
        return JsonPage.restPage(new PageInfo<>(list));
    }

    private void validateTimes(OrderListTimeDTO orderListTimeDTO) {
        // 先获取用户提供的开始和结束时间
        LocalDateTime start=orderListTimeDTO.getStartTime();
        LocalDateTime end=orderListTimeDTO.getEndTime();
        // 为了使业务不太复杂,我们设计start或end任意属性为null,就查询最近一个月订单
        if(start == null || end == null){
            // 开始时间设置为当前时间的一个月之前
            start=LocalDateTime.now().minusMonths(1);
            // 结束时间设置为当前时间
            end=LocalDateTime.now();
            // 将设置好的值赋值到参数中
            orderListTimeDTO.setStartTime(start);
            orderListTimeDTO.setEndTime(end);
        }else{
            // 如果start和end都非null
            // 判断如果end小于start就要抛出异常
            // if(end.isBefore(start)){
            if(end.toInstant(ZoneOffset.of("+8")).toEpochMilli()<
                    start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
                // 结束时间小于开始时间,可以抛出异常
                throw new CoolSharkServiceException(
                        ResponseCode.BAD_REQUEST,"结束时间应大于开始时间!");
            }
        }
    }

    @Override
    public OrderDetailVO getOrderDetail(Long id) {
        return null;
    }


    public CsmallAuthenticationInfo getUserInfo(){
        // 获取SpringSecurity上下文
        UsernamePasswordAuthenticationToken authenticationToken=
                (UsernamePasswordAuthenticationToken)
                        SecurityContextHolder.getContext().getAuthentication();
        // 为了逻辑更加严谨,判断一下SpringSecurity上下文内容是否为null
        if(authenticationToken == null){
            throw new CoolSharkServiceException(
                    ResponseCode.UNAUTHORIZED,"您还未登录");
        }
        // 从SpringSecurity上下文中获取用户信息
        CsmallAuthenticationInfo csmallAuthenticationInfo=
                (CsmallAuthenticationInfo) authenticationToken.getCredentials();
        // 最终返回用户信息
        return csmallAuthenticationInfo;
    }
    // 业务逻辑层实际开发时,大部分都是需要用户的Id而已
    // 所以我们再编写一个方法,只返回用户的Id
    public Long getUserId(){
        return getUserInfo().getId();
    }


}
